package ganga.sensors;

import ganga.custodian.CustodianApplication;
import ganga.custodian.DBHelper;
import ganga.custodian.Helper;
import ganga.sportsman.Application;

import android.content.Context;
import android.database.DatabaseUtils.InsertHelper;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Debug;
import android.util.Log;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.TimerTask;
import java.util.Vector;

/**
 * Класс обеспечивает работу с трёхосным акселерометром. Умеет снимать показания
 * и записывать их в буфер фиксированного размера. Записывает показания
 * акселерометра
 * 
 * @author Apocrypha
 */
public class AccelerometerMonitor {
	private static CustodianApplication application;
	public static SensorManager sensorManager;
	private String[] unreliableDetails = { "Bad accuracy.", "Incorrect values array length.", "Incorrect sensor type." };

	/**
	 * A three-axis accelerometer sensor that returns the current acceleration
	 * along three axes in m/s2.
	 */
	public static Sensor accelerometr;
	public static Sensor orientation;
	public static Sensor gyroscope;

	public boolean isRecording;
	public Timestamp recordStartedAt;
	public long sessionId;

	public boolean running;
	/**
	 * Maximum number of accelerometer measurement stored in buffer
	 */
	final int BUFFER_SIZE = 2048;
	Vector<AccelerometerData> buffer;
	private volatile Listener listener;
	public volatile Transmitter transmitter;
	private DBHelper dbHelper;

	/**
	 * Used for receiving notifications when sensor values have changed.
	 * 
	 * @author Apocrypha
	 * 
	 */
	private class Listener implements SensorEventListener {

		private int counter = 0;

		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// Nothing TO DO
		}

		/**
		 * Copy data from sensor to buffer. Called when sensor values have
		 * changed. This method will be called in other than caller one.
		 * 
		 * @param event
		 *            The sensor event.
		 */
		@Override
		public void onSensorChanged(SensorEvent event) {
			if (Helper.DEBUG)
				if (!isReliable(event))
					return;

			// Copy from event to buffer
			AccelerometerData data = new AccelerometerData(event);
			data.systemTimestamp = System.currentTimeMillis();
			buffer.add(data);

			// For debugging
			counter++;
			if ((counter % 100) == 0) {
				Helper.log("Elements in buffer: " + Integer.toString(buffer.size()));
				counter = 0;
			}
		}// onSensorChanged
	}// Listener

	/**
	 * Перекладывает данные из буфера во внешнее хранилище
	 */
	public class Transmitter implements Runnable {
		public Transmitter() {
		}

		public static final long InitialDelay = 20;
		public static final long Period = 60;

		@Override
		public void run() {
			store();
		}
	}

	public TimerTask transmintAccData;

	/**
	 * Throws during AccelerometerMonitor creation
	 */
	public class InitializeException extends Exception {

		public InitializeException(String message) {
			super(message);
		}

		/**
		 * For serialization purpose
		 */
		private static final long serialVersionUID = -6194059584951775051L;

	}

	public AccelerometerMonitor(CustodianApplication application) throws InitializeException {
		AccelerometerMonitor.application = application;

		sensorManager = (SensorManager) application.context.getSystemService(Context.SENSOR_SERVICE);
		if (sensorManager == null)
			throw new InitializeException("Can not create Sensor Manager");

		accelerometr = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		orientation = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
		gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
		if (accelerometr == null)
			throw new InitializeException("Can not initialize accelerometer.");

		buffer = new Vector<AccelerometerData>(BUFFER_SIZE);
		listener = new Listener();
		transmitter = new Transmitter();
		dbHelper = application.dbHelper;
		
		running = false;
		// isRecording = false;

	}

	/**
	 * Start watching the accelerometer
	 */
	public void start() {
		if (running)
			return;

		running = sensorManager.registerListener(listener, accelerometr, SensorManager.SENSOR_DELAY_NORMAL);

		Helper.log("Accelerometer monitor started.");
	}

	public void stop() {
		sensorManager.unregisterListener(listener, accelerometr);
		running = false;
	}

	public void store() {
		ArrayList<AccelerometerData> data;
		synchronized (buffer) {
			data = new ArrayList<AccelerometerData>(buffer);
			buffer.clear();
		}// lock buffer
		Thread.yield();
		
		SQLiteDatabase dateBase = dbHelper.getWritableDatabase();
		InsertHelper helper = new InsertHelper(dateBase, "Acceleration");

		int counter = 0;
		try {
			dateBase.beginTransaction();
			for (AccelerometerData value : data) {
				helper.prepareForInsert();
				helper.bind(dbHelper.accelerationXColumnInd, value.ax);
				helper.bind(dbHelper.accelerationYColumnInd, value.ay);
				helper.bind(dbHelper.accelerationZColumnInd, value.az);
				helper.execute();
				Thread.yield();
				counter++;
				if ((counter % 2) == 0)
					Thread.yield();// (0, 100000);
			}
			dateBase.setTransactionSuccessful();
		} finally {
			dateBase.endTransaction();
		}
	}

	/**
	 * If the sensor data is unreliable return false
	 * 
	 * @param event
	 * @since 1.1
	 */
	public boolean isReliable(SensorEvent event) {
		// Thread.sleep(100);

		int reason = -1;
		if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE)
			reason = 0;
		if (event.values.length < 3)
			reason = 1;
		if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
			reason = 2;

		if (reason != -1) {
			Log.e(Helper.TAG, "Accelerometer data unreliable. " + unreliableDetails[reason]);
			return false;
		}

		return true;
	}

	public void startRecord(String position, String activity, String movement) {
		if (isRecording)
			return;

		isRecording = true;
		// rowIterator = 0;
		recordStartedAt = new Timestamp(System.currentTimeMillis());
		sessionId = application.dbHelper.insertMeasurementSession(recordStartedAt, position, activity, movement);
	}

	public void stopRecord() {
		isRecording = false;
		long duration = new Timestamp(System.currentTimeMillis()).getTime() - recordStartedAt.getTime();
		application.dbHelper.updateMeasurementSession(sessionId, duration);
		application.dbHelper.close();
	}
}
